package cn.demoncat.util.web;

import cn.demoncat.util.exception.ParamRuntimeException;
import cn.demoncat.util.lang.JsonUtil;
import cn.demoncat.util.lang.constant.StringConstant;
import cn.demoncat.util.lang.entity.MapSs;
import cn.demoncat.util.lang.entity.Result;
import cn.demoncat.util.sec.Md5Util;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * 第三方接口交互 - MD5签名
 * 
 * @author 延晓磊
 *
 * @since 2020年4月9日
 */
public class ApiMd5Util {

	private static final Logger LOG = LoggerFactory.getLogger(ApiMd5Util.class);
	
	// 签名字段
	public static final String signKey = "sign";
	// 密钥字段
	public static final String keyKey = "key";
	// 报文字段
	public static final String bodyKey = "body";
	
	
	/**
	 * 参数签名
	 * 
	 * @param param	请求参数：格式为{参数名:参数值}，参数值统一为String，如果是复杂对象或数组，应使用JSON串
	 * @param key	双方约定的密钥
	 * 
	 * @return 签名
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年4月9日
	 */
	public static String sign(Map<String, String> param, String key) {
		// 转换参数Map为List，键值用=拼接
		List<String> list = new ArrayList<>();
		for (Map.Entry<String, String> entry : param.entrySet()) {
			// 排除空参数和签名字段
			if (StringUtils.isNotBlank(entry.getValue()) && !signKey.equals(entry.getKey())) {
				// k=v
				list.add(entry.getKey() + StringConstant.EQUAL + entry.getValue());
			}
		}
		// 排序List
		Collections.sort(list);
		// 拼接List元素，以&分隔
		String url = StringUtils.join(list, StringConstant.PARAM_SEPARATOR);
		// 拼接字符串的末尾添加KEY密钥
		if (StringUtils.isNotBlank(key)) {
			url = url + StringConstant.PARAM_SEPARATOR + keyKey + StringConstant.EQUAL + key;
		}
		// MD5签名，转换为大写
		return DigestUtils.md5Hex(url).toUpperCase();
	}

	/**
	 * 参数验签
	 * 
	 * @param param	请求参数：格式为{参数名:参数值}，参数值统一为String，如果是复杂对象或数组，应使用JSON串
	 * @param key	双方约定的密钥
	 * 
	 * @return 验证结果
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020年4月9日
	 */
	public static boolean validate(Map<String, String> param, String key) {
		// 签名
		String sign = sign(param, key);
		// 验签
		return sign.equals(param.get(signKey));
	}
	
	/**
	 * 发起请求：签名 > 请求 > 转换Result
	 * 
	 * @param url		第三方接口
	 * @param param		请求参数
	 * @param key		密钥
	 * @return Result
	 * 
	 * @author 延晓磊
	 * @return 
	 *
	 * @since 2020年4月9日
	 */
	public static Result request(String url, Map<String, String> param, String key) {
		// 签名
		param.put(signKey, sign(param, key));
		// 发起请求：url?body=JSON(map)
		return HttpClientUtil.restPost(url, MapSs.getInstance(bodyKey, JsonUtil.toStr(param)));
	}
	
	/**
	 * 接收请求：解析 > 验签
	 * 
	 * @param body		请求报文
	 * @param key		密钥
	 * @return 请求参数
	 * 
	 * @author 延晓磊
	 * @return 
	 *
	 * @since 2020年4月9日
	 */
	public static MapSs response(String body, String key) {
		// 接收参数：JSON.parse(body)
		MapSs map = JsonUtil.parseMapSs(body);
		// 验签
		if (validate(map, key)) {
			return map;
		}else {
			LOG.error("验签失败：\r\n" + body);
			throw new ParamRuntimeException("签验失败");
		}
	}

	/**
	 * 时间戳签名
	 *
	 * @param password	密钥
	 * @return 签名：time{13} + md5(time+password)
	 */
	public static String timeSign(String password){
		String time = String.valueOf(System.currentTimeMillis());
		return time + Md5Util.digest(time + password);
	}

	/**
	 * 时间戳签名校验
	 * @param sign		签名：time{13} + md5(time+password)
	 * @param password	密钥
	 * @return 是否匹配
	 */
	public static boolean timeCheck(String sign, String password){
		String time = sign.substring(0,13);
		return Md5Util.digest(time + password).equals(sign.substring(13));
	}
}
